package com.puzzlebazar.client.main.view;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.puzzlebazar.client.util.Observer;

public abstract class GenericListView extends Composite implements Observer, ClickHandler {

	private static final int VISIBLE_ELEMENT_COUNT = 10;
	private final HTML countLabel = new HTML("",false);
	private final HTML newerButton = new HTML("<a href='javascript:;'>&lt; newer</a>", false);
	private final HTML olderButton = new HTML("<a href='javascript:;'>older &gt;</a>", false);
	private final HTML refreshButton = new HTML("<a href='javascript:;'>refresh</a>", false);
	private int startIndex = 0;
	private int selectedRow = -1;
	private final FlexTable table = new FlexTable();
	private final HorizontalPanel navBar = new HorizontalPanel();
	
	private final int nbColumns;
	
	public GenericListView( int nbColumns ) {
		super();

		this.nbColumns = nbColumns;

		// Setup the table.
		table.setCellSpacing(0);
		table.setCellPadding(0);
		table.setWidth("100%");

		// Hook up events.
		table.addClickHandler(this);
		newerButton.addClickHandler(this);
		olderButton.addClickHandler(this);
		refreshButton.addClickHandler(this);

		// Create the 'navigation' bar at the upper-right.
		HorizontalPanel innerNavBar = new HorizontalPanel();
		navBar.setStyleName(getStylePrefix()+"-NavBar");
		innerNavBar.add(newerButton);
		innerNavBar.add(countLabel);
		innerNavBar.add(olderButton);
		innerNavBar.add(refreshButton);

		navBar.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
		navBar.add(innerNavBar);
		navBar.setWidth("100%");

		initWidget(table);
		setStyleName(getStylePrefix()+"-List");

		initTable();	
	}

	@Override
	public void onClick(ClickEvent event) {
		Object sender = event.getSource();
		
		if (sender == table) {
			int row = table.getCellForEvent(event).getRowIndex();
			selectRow(row - 1);
		}
		else if (sender == olderButton) {
			// Move forward a page.
			startIndex += VISIBLE_ELEMENT_COUNT;
			if ( startIndex >=  getListCount() ) {
				startIndex -= VISIBLE_ELEMENT_COUNT;
			} else {
				selectRow(-1);
				updateView();
			}
		}
		else if (sender == newerButton) {
			// Move back a page.
			if (startIndex != 0) {
				startIndex -= VISIBLE_ELEMENT_COUNT;
				if (startIndex < 0)
					startIndex = 0;
				selectRow(-1);
				updateView();
			}
		}
		else if (sender == refreshButton) {
			// Refresh.
			refreshList();
		}
	}
	/**
	 * Initialises the table so that it contains enough rows for a full page of
	 * elements.
	 */
	protected void initTable() {
		// Create the header row.
		table.getFlexCellFormatter().setColSpan(0, 0, nbColumns);
		table.setWidget(0, 0, navBar);
		table.getRowFormatter().setStyleName(0, getStylePrefix()+"-ListHeader");
		
		CellFormatter cellFormatter = table.getCellFormatter();
		// Initialise the rest of the rows.
		for (int i = 0; i <= VISIBLE_ELEMENT_COUNT; ++i) {
			for (int j=0; j<=nbColumns; ++j) {
				cellFormatter.setWordWrap(i, j, false);
				cellFormatter.setHorizontalAlignment(i, j, HasHorizontalAlignment.ALIGN_LEFT);
			}
		}    
	}

	/**
	 * Selects the given row (relative to the current page).
	 * 
	 * @param row the row to be selected (-1 to deselect all)
	 */
	private void selectRow(int row) {
		// When a row (other than the first one, which is used as a header) is
		// selected, display its associated MailItem.
		int elementIndex = startIndex + row;
	
		styleRow(selectedRow, false);
		if( row != -1 && elementIndex < getListCount() ) {
			styleRow(row, true);
			selectedRow = row;
		}
		else
			selectedRow = -1;
	}

	/**
	 * Change the style of the row to reflect a selection
	 * 
	 * @param row Index of the row (in the current page)
	 * @param selected True to select the row, false to deselect it
	 */
	private void styleRow(int row, boolean selected) {
		if (row != -1) {
			if (selected) {
				table.getRowFormatter().addStyleName(row + 1, getStylePrefix()+"-SelectedRow");
			} else {
				table.getRowFormatter().removeStyleName(row + 1, getStylePrefix()+"-SelectedRow");
			}
		}
	}

	/**
	 * Make sure the view reflects the current internal list of elements. Also
	 * update the information displayed in the header, as well as the button
	 * states.
	 */
	protected void updateView() {
	
		selectRow(-1);
	
		newerButton.setVisible(false);
		olderButton.setVisible(false);
		refreshButton.setVisible(false);
		countLabel.setText("Refreshing list...");
		
		// Clear any remaining slots.
		for (int i=0; i < VISIBLE_ELEMENT_COUNT; ++i) {
			for (int j=0; j < nbColumns; ++j) {
				table.setHTML(i + 1, j, "&nbsp;");
		
			}
		}
		
		DeferredCommand.addCommand( new Command() {
	
			@Override
			public void execute() {
	
				// Update the older/newer buttons & label.
				final int count = getListCount();
				if( startIndex >= count )
					startIndex = count - VISIBLE_ELEMENT_COUNT;
				if( startIndex < 0 )
					startIndex = 0;
				final int max = Math.max( startIndex + VISIBLE_ELEMENT_COUNT, count);
				
				newerButton.setVisible(startIndex != 0);
				olderButton.setVisible(startIndex + VISIBLE_ELEMENT_COUNT < count);
				refreshButton.setVisible( !isRefreshing() );
				if( count > 0 )
					countLabel.setText("" + (startIndex + 1) + " - " + max + " of " + count);
				else {
					if ( isRefreshing() )
						countLabel.setText("Refreshing list...");
					else if ( isValid() )
						countLabel.setText("Empty list");
					else
						countLabel.setText("An error occured");
				}
				
				// Show the selected elements.
				int i = 0;
				for (; i < VISIBLE_ELEMENT_COUNT; ++i) {
					final int currentIndex = startIndex + i;
					
					// Don't read past the end.
					if (currentIndex >= count) {
						break;
					}
	
					addLineToTable( table, i+1, currentIndex );
					
					
					
				}
	
				// Clear any remaining slots.
				for (; i < VISIBLE_ELEMENT_COUNT; ++i) {
					for(int j = 0; j < nbColumns; ++j) {
						table.setHTML(i + 1, j, "&nbsp;");
					}			
				}
			}
		});
	
	}

	/**
	 * Return the prefix of all CSS style name to use. The CSS file should define
	 * prefix-NavBar
	 * prefix-List
	 * prefix-ListHeader
	 * prefix-SelectedRow
	 * 
	 * @return The prefic of all CSS style names to use
	 */
	protected abstract String getStylePrefix();

	/**
	 * Adds a line to the table, populating it with a description of the object for which an index is
	 * passed. This method must populate all the columns of the specified row.
	 * 
	 * @param table The table to operate on
	 * @param row The index of the row to populate
	 * @param currentIndex The index of the object, from the list, to display on that line
	 */
	protected abstract void addLineToTable(FlexTable table, int row, int currentIndex);

	/**
	 * @return The total number of objects in the list
	 */
	protected abstract int getListCount();
	
	/**
	 * @return True if the list is currently refreshing
	 */
	protected abstract boolean isRefreshing();

	/**
	 * @return True if the list is valid
	 */
	protected abstract boolean isValid();


	/**
	 * Refresh the list
	 */
	protected abstract void refreshList();
	
	
}